using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using Needle.Core.Lifetime;
using Needle.Utils;
using Needle.Utils.Extensions;
using Vertex = Needle.Core.Activation.DependencyGraph.Vertex;

namespace Needle.Core.Activation
{

    /// <summary>
    /// The ActivationContext holds all activators.
    /// </summary>
    public class ActivationManager : NeedleContext, IActivationManager
    {

        #region fields

        private ActivatorCollection activators;

        #endregion

        #region constructor

        /// <summary>
        /// initializes a new instance.
        /// </summary>
        public ActivationManager()
        {
            activators = new ActivatorCollection(this);
        }

        #endregion

        #region ICollection<IActivator> Members

        /// <summary>
        /// Adds an activator to the context.
        /// </summary>
        /// <param name="item"></param>
        public void Add(IActivator item)
        {
            activators.Add(item);
            base.Add(item);

        }

        /// <summary>
        /// Clears and disposes all activators.
        /// </summary>
        public void Clear()
        {
            activators.Clear(delegate(IActivator a)
            {
                a.Dispose();
            });
        }
        /// <summary>
        /// Returns true if this context contains the activator.
        /// </summary>
        public bool Contains(IActivator item)
        {
            return activators.Contains(item);
        }
        /// <summary>
        /// Copies the activators to the given array starting at the given index.
        /// </summary>
        public void CopyTo(IActivator[] array, int arrayIndex)
        {
            activators.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// The number of activators in this context.
        /// </summary>
        public int Count
        {
            get { return activators.Count; }
        }

        /// <summary>
        /// returns false.
        /// </summary>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// removes the given activator  from the context.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Remove(IActivator item)
        {
            bool removed = activators.Remove(item);
            base.Remove(item);
            return removed;
        }

        #endregion

        #region IEnumerable<IActivator> Members

        /// <summary>
        /// returns an enumeration of all the activators in this context.
        /// </summary>
        public IEnumerator<IActivator> GetEnumerator()
        {
            return activators.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return activators.GetEnumerator();
        }

        #endregion

        #region IServiceProvider Members

        /// <summary>
        /// Returns a service implementing the given type, or null.
        /// </summary>

        protected override object GetService(Type t)
        {
            if (typeof(IActivationManager) == t)
                return this;
            return base.GetService(t);
        }

        #endregion

        #region Overrides

        /// <summary></summary>
        public override void BeginInit()
        {
            base.BeginInit();
            GetService<IServiceContainer>().AddService<IActivationManager>(this, true);
        }

        /// <summary>
        /// Called when the instance is disposed.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                Clear();
            }
            base.Dispose(disposing);
        }

        #endregion

        #region IActivationContext Members
        /// <summary>
        ///  
        /// </summary>
        public object CreateInstance(InstanceId instanceId)
        {
            using (IActivationContext ctx = CreateContext())
            {
                return ctx.GetInstance(instanceId);
            }
        }
        /// <summary>
        ///  
        /// </summary>
        private ActivationContext CreateContext()
        {
            if (_CurrentContext != null)
                throw new InvalidOperationException("Another activation is already in progress");
            _CurrentContext = new ActivationContext(this);
            return _CurrentContext;
        }

        /// <summary>
        ///  
        /// </summary>
        public void ConfigureInstance(InstanceId instanceId, object instance)
        {
            using (IActivationContext ctx = CreateContext())
            {
                ctx.ConfigureInstance(instanceId, instance);
            }
        }

        ActivationContext _CurrentContext=null;
        /// <summary>
        ///  
        /// </summary>
        public IActivationContext CurrentContext
        {
            get {  return _CurrentContext; }
        }
        /// <summary>
        /// Returns the activator registered for the given id.
        /// </summary>
        /// <param name="id">The id of the activator.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentException">
        /// Throws an argument exception if there is no activator for the id.
        /// </exception>
        public IActivator GetActivator(InstanceId id)
        {
            IActivator activator = null;
            if (!activators.TryGetValue(id, out activator))
            {
                activator = new ReflectionActivator(id, id.Type);
                this.Add(activator);
                this.Validate();
            }
            return activator;
        }
        /// <summary>
        /// Returns true if the activator was found, false otherwise.
        /// activator is set to the activator or null.
        /// </summary>
        /// <param name="id">The id of the activator.</param>
        /// <param name="activator" >ref parameter to return the activator.</param>
        /// <returns></returns>
        public bool TryGetActivator(InstanceId id, ref IActivator activator)
        {
            return activators.TryGetValue(id, out activator);
        }

        /// <summary>
        /// Validates that there are no dependency cycles in the activators, and that all dependencies are activatable.
        /// </summary>
        public void Validate()
        {
            DependencyGraph graph = new DependencyGraph();
            foreach (IActivator a in this)
            {
                InstanceId id = a.Id;
                Vertex v = graph.CreateOrGetVertex(id);

                if (v.Tag == null)
                {
                    v.Tag = a;
                    foreach (InstanceId depId in a.Info.Constructor.Dependencies)
                    {
                        graph.CreateOrGetVertex(depId);
                        graph.AddEdge(id, depId);
                    }
                }
            }
            List<InstanceId> unresolvedDependencies = new List<InstanceId>();
            foreach (Vertex v in graph.Vertices)
            {
                if (v.Tag == null)
                    unresolvedDependencies.Add(v.Id);
            }

            if (unresolvedDependencies.Count > 0)
            {
                Debug.WriteLine("Unresolved dependencies:");
                unresolvedDependencies.ForEach(x => Debug.WriteLine(x));
            }
            graph.IsLocked = true;
            foreach (IActivator a in this)
            {
                graph.IsDagFor(a.Id, true);
            }
        }

        #endregion
        
        #region ActivationContext

        private class ActivationContext : IActivationContext
        {
            ActivationManager manager;
            LifetimeManager tracker;

            public ActivationContext(ActivationManager manager)
            {
                this.manager = manager;
                tracker = new LifetimeManager();
                tracker.Initialize();
            }

            #region IActivationContext Members

            public object GetInstance(InstanceId id)
            {
                object instance = null;

                instance = tracker.GetInstance(id);
                if (instance != null)
                    return instance;

                ILifetimeManager lifetimeManager = manager.GetService<ILifetimeManager>();
                instance = lifetimeManager.GetInstance(id);
                if (instance != null)
                    return instance;

                IActivator activator = manager.GetActivator(id);
                instance = activator.CreateInstance();
                tracker.Register(id, instance);
                activator.ConfigureInstance(instance);
                return instance;
            }
            public void ConfigureInstance(InstanceId id, object instance)
            {
                Ensure.NotNull(instance, "instance");
                tracker.Register(id, instance);
                IActivator activator = manager.GetActivator(id);
                activator.ConfigureInstance(instance);
  
            }

            public void Register(InstanceId instanceId, object instance, ITrackingPolicy trackingPolicy)
            {
                manager.GetService<ILifetimeManager>().Register(instanceId, instance, trackingPolicy);
            }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                tracker.Dispose();
                tracker = null;
                manager._CurrentContext = null;
                manager = null;
            }

            #endregion

        }

        #endregion

    }
}
